/*
 * $AIST_Release: 0.9.0 $
 * Copyright 2011 Information Technology Research Institute, National
 * Institute of Advanced Industrial Science and Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package parser.analyzer

import scala.util.parsing.input.Position
import scala.collection.JavaConversions.asBuffer
import scala.PartialFunction.condOpt
import parser.Nodes._
import parser.DataTypes._
import parser.Messages
import parser.Results._
import parser.TreeWalker

/**
 * Expression and initializer analyzer.
 */
trait ExprAnalyzer {

  /**
   * Reference number and real data type map.
   */
  val typeMap: Map[Int, DataType]

  /**
   * Finds type converter.
   */
  def findConverter(from: DataType, to: DataType): Option[Expr]

  /**
   * Resolves variable.
   */
  def resolveVariable(v: Variable): Result[Expr]

  /**
   * Gets enum value.
   */
  def getEnumValue(e: String, elems: String): Result[Expr]

  /**
   * Handles function literal.
   */
  def handleFuncLit(func: FuncLit): Result[(List[Arg], List[Stmt])]

  /**
   * Makes type mismatch error.
   */
  def typemismatch(found: Expr, required: DataType*) = List(Messages.error(found.pos,
"""type mismatch. 
  found   : %s
  required: %s""", getName(typeMap, found.dataType), required.map{getName(typeMap, _)}.mkString(" or ")))

  /**
   * Checks data type.
   */
  def checkType(expr: Expr, expects: DataType*): Result[Unit] = {
    if (expects.exists{matchType(typeMap, expr.dataType, _)}) {
      Success(Unit)
    } else {
      Failure(typemismatch(expr, expects:_*))
    }
  }

  /**
   * Analyze variable initializer.
   */
  def analyzeInitilizer(i: Initializer, expectType: DataType): Result[Expr] = i match {
    case ExprInitializer(expr) => 
      analyzeExpr(expr).map(stripRefExpr).flatMap { expr =>
        condOpt((expr, expectType))(handleBuildinFunctionAssignment).getOrElse {
          if (matchType(typeMap, expr.dataType, expectType)) Success(expr)
          else findConverter(expr.dataType, expectType) match {
             case Some(func) => Success(FuncCall(func, List(expr)))
             case None       => Failure(typemismatch(expr, expectType))
          }
        } 
      }
    case MapInitializer(items) => resolveType(typeMap, expectType) match {
      case MapType(keyType, valueType) => 
        val l = items.map { case (key, value) => mkTuple(analyzeInitilizer(key, keyType), analyzeInitilizer(value, valueType)) }
        mergeList(l).map(MapExpr(_, keyType, valueType))
      case _ => failure(i.pos, "Type mismatch. Initial value is map type but variable type is not map")
    }
    case ListInitializer(items) => resolveType(typeMap, expectType) match {
      case MapType(keyType, valueType) if (items.isEmpty) => Success(MapExpr(List(), keyType, valueType))
      case ArrayType(t)                                   => mergeList(items.map{analyzeInitilizer(_, t)}).map(ArrayExpr(_, t))
      case t: TupleType => 
        if (items.length != t.members.length) { 
          failure(i.pos, "Tuple %s has %d members, but length of this initializer is %d.", t, t.members.length, items.length)
        } else {
          mergeList(items.zip(t.members.unzip._2).map({ case (item, dataType) => analyzeInitilizer(item, dataType) })).map(TupleExpr(_, t))
        }
      case _ => 
      failure(i.pos, "Type mismatch. Initial value is { ... } but variable type is not array nor tuple: %s", expectType)
    }
    case _ => failure(i.pos, "Type mismatch. Variable type is not array nor tuple")
  }

  /**
   * Analyzes expression.
   */
  def analyzeExpr(expr: Expr): Result[Expr] = analyzeExprImpl(expr).map(_.setPos(expr.pos))

  private def isAssignable(expr: Expr) = expr.dataType.isInstanceOf[RefType]
  private val compAssign = "([*+-/%])=".r
  private def analyzeExprImpl(expr: Expr): Result[Expr] = {
    TreeWalker.Mapper(classOf[Expr]).setPreprocess {
      case expr@BinOp(compAssign(origOp), left, right, _) => // Syntax Sugar
        Success(BinOp("=", left, BinOp(origOp, left, right).setPos(expr.pos)).setPos(expr.pos))
    }.setFollower {
      f => {
        case expr@MemAccOp(tuple, member, _) =>
          val r = tuple match {
            case v: Variable => resolveVariable(v).map{_ => Unit}
            case _ => Success(Unit)
          }

          r match {
            case x: Failure =>
              tuple match {
                case Variable(n) => 
                  val e = getEnumValue(n, member)
                  if (e.successful) e
                  else              x
                case _ => x
              }
            case _ =>
              f(tuple).flatMap { case tuple: Expr =>
                resolveType(typeMap, stripRef(tuple.dataType)) match {
                  case t: TupleType =>
                    t.members.find(_._1 == member) match {
                      case Some((n, t)) => Success(MemAccOp(tuple, member, sucRef(tuple.dataType, t)))
                      case None => failure(expr.pos, "%s has a member of name \"%s\"", tuple.dataType, member)
                    }
                  case _ => failure(expr.pos, "Left hand side is not tuple.")
                }
              }
          }
      }
    }.setPostprocess {
      case expr: BinOp => analyzeBinOp(expr)
      case expr: UnOp => analyzeUnOp(expr)
      case v: Variable => resolveVariable(v) 
      case expr@FuncCall(func, args) =>
        val argsType = args.map{x => stripRef(x.dataType)}

        func match {
          case GlobalVariable(name, FunctionList(funcs), _) =>
            funcs.find { x => matchTypeList(typeMap, x.argsType, argsType) } match {
              case Some(ft) => Success(FuncCall(BuildInFunction(name, ft), args.map(stripRefExpr)))
              case None => failure(expr.pos, "Invalid function type to %s(%s)".format(name, argsType.mkString(", ")))
            }
          case _ =>
            stripRef(func.dataType) match {
              case f: FunctionType if (matchTypeList(typeMap, f.argsType, argsType)) =>
                Success(FuncCall(stripRefExpr(func), args.map(stripRefExpr)))
              case _ =>
                failure(expr.pos, "Invalid function type to (%s)".format(argsType.mkString(", ")))
            }
        }
      case expr: FuncLit =>
        handleFuncLit(expr).map { case (args, stmts) =>
          AnalyzedFuncLit(expr.ret, args, stmts).setPos(expr.pos)
        }

      case x:IntLit    => Success(x)
      case x:StringLit => Success(x)
      case x:FloatLit  => Success(x)
      case _: AndOp             => error("Unexpect expression")
      case _: ArrayExpr         => error("Unexpect expression")
      case _: AssignOp          => error("Unexpect expression")
      case _: ContainerAccessOp => error("Unexpect expression")
      case _: MapExpr           => error("Unexpect expression")
      case _: OrOp              => error("Unexpect expression")
      case _: TupleExpr         => error("Unexpect expression")
      case _: ValueExpr         => error("Unexpect expression")
    }.map(expr).map { _.asInstanceOf[Expr] }
  }

  private val handleBuildinFunctionAssignment: PartialFunction[(Expr, DataType), Result[Expr]] = {
    case (e@GlobalVariable(name, flist: FunctionList, _), f: FunctionType) =>
      flist.funcs.filter { matchType(typeMap, _, f) } match {
        case Nil     => failure(e.pos, "Type mismatch required %s, but [%s].", f, flist.funcs.mkString(" or "))
        case List(_) => Success(BuildInFunction(name, f))
        case fs      => failure(e.pos, "Matched types are two or more(%s).", fs.mkString(" or "))
      }
  }

  private def analyzeBinOp(expr: BinOp) = {
    val BinOp(op, left, right, _) = expr
  
    def checkArgsType(left: Expr, right: Expr, expects: DataType*) = {
      checkType(left, expects:_*).flatMap { _ => checkType(right, left.dataType) }
    }
      
    op match {
      case "=" =>
        if (!isAssignable(left)) {
          failure(expr.pos, "Left side expression is not assignable.")
        } else {
          condOpt((right, stripRef(left.dataType)))(handleBuildinFunctionAssignment).getOrElse {
            checkType(right, left.dataType).mapConst(right)
          }.map { right =>
            AssignOp(left, stripRefExpr(right)).setPos(expr.pos)
          }
        }

      case "[]" =>
        def f(expectKeyType: DataType, actualKey: Expr, container: Expr, valueType: DataType): Result[Expr] = {
          checkType(actualKey, expectKeyType).mapConst {
            ContainerAccessOp(container, stripRefExpr(actualKey), sucRef(container.dataType, valueType))
          }
        }
        stripRef(left.dataType) match {
          case MapType(key, value)                 => f(key, right, left, value)
          case ArrayType(value)                    => f(IntType, right, left, value)
          case BytesType                           => f(IntType, right, left, IntType)
          case StringType                          => f(IntType, right, left, IntType)
          case _ => failure(left.pos, "Subscripted value is neither array, map, string, nor bytes.")
        }

      case "&&" | "||" =>
        val Op = if (op == "&&") AndOp else OrOp
        checkArgsType(left, right, IntType).mapConst(Op(stripRefExpr(left), stripRefExpr(right)))

      case "==" | "!=" => 
        checkType(right, left.dataType).mapConst(pureBinOp(expr, IntType))

      case "+" => 
        checkArgsType(left, right, IntType, FloatType, StringType).mapConst(pureBinOp(expr, left.dataType))

      case "-" | "*" | "/" | "%" => 
        checkArgsType(left, right, IntType, FloatType).mapConst(pureBinOp(expr, left.dataType))

      case "<" | ">" | "<=" | ">=" => 
        checkArgsType(left, right, IntType, FloatType).mapConst(pureBinOp(expr, IntType))

      case _ =>
        failure(expr.pos, "Unkown operator: %s", expr.op)
    }
  }

  private def analyzeUnOp(expr: UnOp) = {
    val UnOp(op, arg, _) = expr
    op match {
      case "++" | "--" =>
        val oriop = op(0).toString

        if (!isAssignable(arg)) failure(expr.pos, "The expression is not incrementable/decrementable.")
        else checkType(arg, IntType).mapConst {
          AssignOp(arg, BinOp(oriop, stripRefExpr(arg), IntLit(1), IntType).setPos(expr.pos))
        }

      case "+" | "-" =>
        checkType(arg, IntType, FloatType).mapConst(pureUnOp(expr, arg.dataType))

      case "!" =>
        checkType(arg, IntType).mapConst(pureUnOp(expr, IntType))

      case _ =>
        failure(expr.pos, "Unkown operator: %s", expr.op)
    }
  }

  private def pureBinOp(expr: BinOp, dataType: DataType) = {
    BinOp(expr.op, stripRefExpr(expr.left), stripRefExpr(expr.right), stripRef(dataType)).setPos(expr.pos)
  }

  private def pureUnOp(expr: UnOp, dataType: DataType) = {
    UnOp(expr.op, stripRefExpr(expr.arg), stripRef(dataType)).setPos(expr.pos)
  }
}
